/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Permissions;
using System.Diagnostics.CodeAnalysis;

namespace CsJni
{
    public sealed class JniClass : JniObject
    {
        public const char JavaTypeDelimiter = '.';
        public const char JniTypeDelimiter = '/';

        private static JniMethod mGetNameMid; // TODO - review statics
        private static bool mStaticsInitialized = false;

        internal JniClass(JniEnvironment environment, IntPtr handle, JniObjectLifecycle lifecycle)
            : base(environment, handle, lifecycle)
        {
            // TODO - make this instance global???
            if (!mStaticsInitialized)
            {
                mStaticsInitialized = true;
                JniClass localClassClass = this.GetObjectClass(environment);
                // NOTE: no need to NewGlobalRef class handle for java.lang.Class, will not be unloaded
                mGetNameMid = localClassClass.GetMethodId(environment, "getName", "()Ljava/lang/String;");
            }
        }

        public override JniConcreteObjectType ConcreteType
        {
            get { return JniConcreteObjectType.Class; }
        }

        public string GetTypeName(JniEnvironment environment)
        {
            VerifyEnvironment(environment);
            return mGetNameMid.CallStringMethod(environment, this, null);
        }

        public static string ToJniClassName(string reflectionClassName)
        {
            return reflectionClassName.Replace(JniClass.JavaTypeDelimiter, JniClass.JniTypeDelimiter);
        }

        public JniField GetFieldId(
            JniEnvironment environment,
            string name,
            string signature
            )
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.GetFieldID(environment.Handle, this.Handle, name, signature);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return new JniField(result, this, false, signature);
        }

        public JniField GetStaticFieldId(
            JniEnvironment environment,
            string name,
            string signature
            )
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.GetStaticFieldID(environment.Handle, this.Handle, name, signature);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return new JniField(result, this, true, signature);
        }

        public JniMethod GetMethodId(
            JniEnvironment environment,
            string name,
            string signature
            )
        {
            return new JniMethod(GetMethodIdCore(environment, name, signature), this, false, signature);
        }

        public JniConstructor GetConstructorId(
            JniEnvironment environment,
            string signature
            )
        {
            return new JniConstructor(GetMethodIdCore(environment, JniConstructor.JniConstructorName, signature), this, signature);
        }

        private IntPtr GetMethodIdCore(
            JniEnvironment environment,
            string name,
            string signature
            )
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.GetMethodID(environment.Handle, this.Handle, name, signature);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return result;
        }

        public JniMethod GetStaticMethodId(
            JniEnvironment environment,
            string name,
            string signature
            )
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.GetStaticMethodID(environment.Handle, this.Handle, name, signature);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return new JniMethod(result, this, true, signature);
        }

        public static JniClass FindClass(
            JniEnvironment environment,
            string name
            )
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.FindClass(environment.Handle, name);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return new JniClass(environment, result, JniObjectLifecycle.Local);
        }

        public JniClass GetSuperclass(
            JniEnvironment environment)
        {
            VerifyEnvironment(environment);
            IntPtr result = JniEnvironment.Functions.GetSuperclass(environment.Handle, this.Handle);
            JniThrowable.CheckException(environment);
            return new JniClass(environment, result, JniObjectLifecycle.Local);
        }

        // OK to suppress, match JNI name & signature
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static bool IsAssignableFrom(
            JniEnvironment environment,
            JniClass subclass,
            JniClass superclass
            )
        {
            VerifyEnvironment(environment);
            VerifyInputJniObject(subclass);
            VerifyInputJniObject(superclass);
            byte result = JniEnvironment.Functions.IsAssignableFrom(environment.Handle, subclass.Handle, superclass.Handle);
            JniThrowable.CheckException(environment);
            return JBooleanConverter.From(result);
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        internal void RegisterNatives(
            JniEnvironment environment,
            JniNativeMethodHolder holder
            )
        {
            VerifyEnvironment(environment);
            VerifyInputObject(holder);
            DelegatePin[] pins = null;
            JniNativeMethod[] methods = holder.ToJniNativeMethodArray(out pins);
            JavaVM.AddDelegatePins(pins);
            try
            {
                int result = JniEnvironment.Functions.RegisterNatives(environment.Handle, this.Handle, methods, methods.Length);
                if (0 != result)
                {
                    JniException.CheckErrorCode(environment, result);
                }
                // EXCEPTION CHECK: Do nothing, already checked for exception above.
            }
            finally
            {
                JniNativeMethodHolder.FreeArrayMemory(methods);
            }
        }
    }
}
